package com.fjeld.server.store.reader;

import javax.xml.bind.JAXBException;
import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

import com.fjeld.middleware.util.FileKit;
import com.fjeld.middleware.jaxb.*;
import com.fjeld.middleware.Progress;
import com.fjeld.server.store.StorageReader;
import org.apache.log4j.Logger;

/**
 * Loads the nodes from persistant storage and organises them in useful maps. These items can
 * then be retrieved either all nodes, all tree nodes, all content nodes or a particular
 * node by ID.
 */
public class XMLPersistanceFileReader extends XMLKit implements StorageReader
{
    private static final Logger logger = Logger.getLogger(XMLPersistanceFileReader.class);
    private static final ObjectFactory factory = new ObjectFactory();
    private static FileKit fileKit = FileKit.getInstance();

    private V1List objectList;
    private Map<String, V1Node> nodeMap = new HashMap<String, V1Node>(100);
    private Map<String, V1Node> nodeTreeMap = new HashMap<String, V1Node>(100);
    private Map<String, V1Node> nodeContentMap = new HashMap<String, V1Node>(100);
    private String fileName;

    public XMLPersistanceFileReader(String fileName)
    {
        this.fileName = fileName;
        try
        {
            File dataFile = getFile(this.fileName);
            Object object = um.unmarshal(dataFile);
            objectList = (V1List) object;

            logger.info("Nodes in file: " + objectList.getV1Node().size());
            for (V1Node node : objectList.getV1Node())
            {
                addNodeToRespectiveMaps(node);
            }
            logger.info("Loaded " + nodeMap.keySet().size() + " nodes alltogether.");
            logger.info("Loaded " + nodeTreeMap.keySet().size() + " tree nodes.");
            logger.info("Loaded " + nodeContentMap.keySet().size() + " content nodes.");


        } catch (JAXBException e)
        {
            e.printStackTrace();
        }
    }

    private void addNodeToRespectiveMaps(V1Node node)
    {
        logger.info("Adding: " + XMLKit.xmlObjToString(node));
        nodeMap.put(node.getNodeID(), node);

        if (null != node.getContentID() && !"".equals(node.getContentID()))
        {
            logger.info("  - Also adding to tree map");
            nodeTreeMap.put(node.getNodeID(), node);
        } else
        {
            logger.info("  - Also adding to content map");
            nodeContentMap.put(node.getNodeID(), node);
        }
    }

    private void removeNode(V1Node node)
    {
        removeNodeFromMaps(node.getNodeID());

        // Update the node list
        objectList.getV1Node().clear();
        for (V1Node tempNode : nodeMap.values())
        {
            objectList.getV1Node().add(tempNode);
        }

    }

    private void removeNodeFromMaps(String nodeID)
    {
        if (null == nodeID || "".equals(nodeID))
        {
            return;
        }

        V1Node nodeToRemove = get(nodeID);
        // Remove from maps
        nodeMap.remove(nodeToRemove.getNodeID());
        nodeTreeMap.remove(nodeToRemove.getNodeID());
        nodeContentMap.remove(nodeToRemove.getNodeID());

        // Remove children
        for (V1ID childRef : nodeToRemove.getV1ID())
        {
            removeNodeFromMaps(childRef.getNodeID());
        }

        // Remove content node
        removeNodeFromMaps(nodeToRemove.getContentID());
    }

    public V1List getObjectList()
    {
        return objectList;
    }

    /**
     * Returns a particular V1Node
     * @param nodeID
     * @return
     */
    public V1Node get(String nodeID)
    {
        V1Node node = nodeMap.get(nodeID);
        logger.info("Fetching node ["+nodeID+"]: " + node);
        return node;
    }

    /**
     * Returns the list of nodes in the tree.
     * @return
     */
    public V1List getTreeList()
    {
        Collection<V1Node> collection = nodeTreeMap.values();
        V1List v1List = factory.createV1List();
        List<V1Node> list = v1List.getV1Node();
        for (V1Node node : collection)
        {
            list.add(node);
        }


        logger.info("Tree nodes returning: " + list.size());
        return v1List;
    }

    /**
     * Loops over all the lists it has a conecept over and try to find the item with the corresponding ID
     * in the server request object
     * Returns the found item in a V1List.
     * @param request
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public V1List getEntryByID(V1ServerRequest request) throws InvocationTargetException, IllegalAccessException
    {
        String id = request.getValue();

        for (Method method : objectList.getClass().getDeclaredMethods())
        {
            if (method.getReturnType().equals(List.class))
            {
                List<V1ID> list = (List<V1ID>) method.invoke(objectList);
                for (V1ID item : list)
                {
                    if (item.getNodeID().equals(id))
                    {
                        V1List v1List = factory.createV1List();
                        List theList = v1List.getV1Node();
                        theList.add(item);
                        return v1List;
                    }
                }

            }
        }

        return null;
    }

    public V1List addTreeChildNodeToParent(V1ServerRequest request)
    {
        String name = request.getValue();
        String parentID = request.getNodeReferenceValue();
        logger.info("Adding [" + name + "] to parent [" + parentID + "]");


        // Create a content node associated with the tree node
        V1Node newContentNode = factory.createV1Node();
        newContentNode.setName(name);
        newContentNode.setNodeID(UUID.randomUUID().toString());
        addNodeToRespectiveMaps(newContentNode);

        // Create the new tree child
        V1Node newNode = factory.createV1Node();
        newNode.setName(name);
        newNode.setNodeID(UUID.randomUUID().toString());
        newNode.setWriteLock(false);
        newNode.setParentID(parentID);
        newNode.setContentID(newContentNode.getNodeID());
        addNodeToRespectiveMaps(newNode);


        // Add the child's id in under the parent
        V1Node parentNode = get(parentID);
        V1ID parentToChildReference = factory.createV1ID();
        parentToChildReference.setNodeID(newNode.getNodeID());
        parentNode.getV1ID().add(parentToChildReference);

        // Copy the parent's environments and actions
        for (V1ID v1ID : parentNode.getV1ID())
        {
            V1Node v1Node = get(v1ID.getNodeID());
            logger.debug("Content/env/action? " + v1Node.getName());
        }

        save();

        V1List list = factory.createV1List();
        list.getV1Node().add(newContentNode);
        list.getV1Node().add(newNode);
        list.getV1Node().add(parentNode);
        return list;
    }

    public V1List addEnvironmentToNode(V1ServerRequest request)
    {
        String name = request.getValue();
        String parentID = request.getNodeReferenceValue();
        logger.info("Adding environment [" + name + "] to node [" + parentID + "]");


        // Create the new environment node
        V1Node newNode = factory.createV1Node();
        newNode.setName(name);
        newNode.setNodeID(UUID.randomUUID().toString());
        newNode.setWriteLock(false);
        newNode.setParentID(parentID);
        addNodeToRespectiveMaps(newNode);

        // Add the child's id in under the parent
        V1ID parentToChildReference = factory.createV1ID();
        parentToChildReference.setNodeID(newNode.getNodeID());
        V1Node parentNode = get(parentID);
        parentNode.getV1ID().add(parentToChildReference);
        save();

        V1List list = factory.createV1List();
        list.getV1Node().add(newNode);
        list.getV1Node().add(parentNode);
        return list;
    }

    public V1List addActionToEnvironment(V1ServerRequest request)
    {
        String name = request.getValue();
        String parentID = request.getNodeReferenceValue();
        logger.info("Adding action [" + name + "] to env node [" + parentID + "]");


        // Create the new action node
        V1Node newNode = factory.createV1Node();
        newNode.setName(name);
        newNode.setNodeID(UUID.randomUUID().toString());
        newNode.setWriteLock(false);
        newNode.setParentID(parentID);
        newNode.setStatus(Progress.NotStarted.toString());
        addNodeToRespectiveMaps(newNode);

        // Add the child's id in under the parent
        V1ID parentToChildReference = factory.createV1ID();
        parentToChildReference.setNodeID(newNode.getNodeID());
        V1Node parentNode = get(parentID);
        parentNode.getV1ID().add(parentToChildReference);
        save();

        V1List list = factory.createV1List();
        list.getV1Node().add(newNode);
        list.getV1Node().add(parentNode);
        return list;
    }

    public V1List removeNode(V1ServerRequest request)
    {
        String nodeID = request.getValue();
        V1Node nodeToRemove = get(nodeID);

        // Remove from parent
        V1Node parentNode = get(nodeToRemove.getParentID());
        for (V1ID childref : parentNode.getV1ID())
        {
            if (childref.getNodeID().equals(nodeID))
            {
                parentNode.getV1ID().remove(childref);
                break;
            }
        }

        // Remove from maps
        removeNode(nodeToRemove);
        save();

        // Publish updated parent node
        V1List list = factory.createV1List();
        list.getV1Node().add(parentNode);
        return list;
    }

    public V1List setContent(V1ServerRequest request)
    {
        String contentNodeID = request.getNodeReferenceValue();
        String text = request.getValue();
        logger.info("Adding text to node [" + contentNodeID + "] and text is [" + text + "]");

        V1Node contentNode = get(contentNodeID);
        contentNode.setV1Content(text);
        contentNode.setHostname(request.getHostname());
        contentNode.setUsername(request.getUsername());
        contentNode.setPassword(request.getPassword());

        // Publish updated parent node
        V1List list = factory.createV1List();
        list.getV1Node().add(contentNode);
        return list;
    }

    public V1List setStatus(V1ServerRequest request)
    {
        String contentNodeID = request.getNodeReferenceValue();
        String status = request.getValue();
        logger.info("Setting status of node [" + contentNodeID + "] to [" + status + "]");

        V1Node contentNode = get(contentNodeID);
        contentNode.setStatus(status);

        // Publish updated parent node
        V1List list = factory.createV1List();
        list.getV1Node().add(contentNode);
        return list;
    }

    public void save()
    {
        // Make sure the objectlist to be saved is updated, i.e. that it
        // contains every node created and has every node removed that was deleted.
        objectList.getV1Node().clear();
        for (V1Node tempNode : nodeMap.values())
        {
            objectList.getV1Node().add(tempNode);
        }

        // Save it
        File saveFile = fileKit.createFile("misc/"+ fileName +".xml");
        saveFile(saveFile, objectList);
    }

}